#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
柔性作业车间调度问题实现
包含编码、解码、甘特图绘制等功能
"""

import os
from typing import Dict, List, Tuple

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib.pylab import mpl

from config.config import MAINTENANCE_MACHINE, MAINTENANCE_TIME, MAINTENANCE_DURATION
from src.utils import get_experiment_dir

mpl.rcParams['font.sans-serif'] = ['SimHei']  # 添加这条可以让图形显示中文
mpl.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题


class FlexibleJobShop:
    """柔性作业车间调度问题实现类"""

    def __init__(self, flag: int, filename: str, order_num: int, machine_num: int, pi: float,
                 parm_data: List, jobing=None, insertpoint=None, jobdic=None, nojob=None):
        """
        初始化柔性作业车间调度问题
        
        Args:
            flag: 调度类型标志，1表示重调度，0表示普通调度
            filename: 机器功率数据文件
            order_num: 订单数量
            machine_num: 机器数量
            pi: 随机挑选机器的概率
            parm_data: 包含订单信息、交付时间、工作信息的数据
            jobing: 重调度情况下正在进行的作业
            insertpoint: 重调度插入点
            jobdic: 作业字典
            nojob: 不需要调度的作业
        """
        self.flag = flag  # 如果为1就是重调度，为0就是普通调度
        self.order_num = order_num  # 订单数
        self.machine_num = machine_num  # 机器数
        self.pi = pi  # 随机挑选机器的概率
        self.info, self.delivery_time, self.work = parm_data[0], parm_data[1], parm_data[2]
        self.jobing = jobing
        self.insertpoint = insertpoint
        self.jobdic = jobdic
        self.nojob = nojob
        data = pd.read_excel(filename, index_col=0)
        self.p1 = data.loc['负载'].values.tolist()
        self.p2 = data.loc['功率'].values.tolist()

        # 初始化原始调度信息缓存
        self._original_schedule_cache = None
        
        # 如果是重调度，预加载原始调度信息
        # 1 插单重嗲都
        # 2 预测性维护重调度
        if self.flag == 1 or self.flag == 2:
            # 预加载并缓存结果，但不重复赋值
            _ = self.load_original_schedule()

    def get_axis_info(self) -> Tuple[List[str], List[int]]:
        """获取坐标轴信息
        
        Returns:
            机器标签和刻度列表
        """
        index = ['M1', 'M2', 'M3', 'M4', 'M5', 'M6', 'M7', 'M8', 'M9', 'M10', 'M11', 'M12',
                 'M13', 'M14', 'M15', 'M16', 'M17', 'M18', 'M19', 'M20']
        scale_ls, index_ls = [], []
        for i in range(self.machine_num):
            scale_ls.append(i + 1)
            index_ls.append(index[i])
        return index_ls, scale_ls  # 返回坐标轴信息，按照工件数返回，最多画20个机器

    def encoding(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
        """
        对问题进行编码
        
        Returns:
            工序编码、机器编码和时间编码
        """
        job = np.copy(self.work)  # 工序编码
        np.random.shuffle(job)  # 打乱顺序
        job = np.array(job).reshape(1, len(self.work))  # 维度：[1,N] 

        ccount = np.zeros((1, self.order_num), dtype=np.int32)  # [1,order_num]
        machine = np.ones((1, job.shape[1]))  # [1,N]
        machine_time = np.ones((1, job.shape[1]))  # 初始化矩阵

        for i in range(job.shape[1]):
            oper = int(job[0, i])

            if np.random.rand() > self.pi:  # 选取最小加工时间机器
                minM = min(self.info[oper][ccount[0, oper]].items(), key=lambda x: x[1])[0]
                machine[0, i] = minM
                machine_time[0, i] = self.info[oper][ccount[0, oper]][minM]
            else:  # 否则随机挑选机器
                n_machine = list(self.info[oper][ccount[0, oper]].keys())
                n_time = list(self.info[oper][ccount[0, oper]].values())
                index = np.random.randint(0, len(n_machine), 1)
                machine[0, i] = n_machine[index[0]]
                machine_time[0, i] = n_time[index[0]]

            ccount[0, oper] += 1

        return job, machine, machine_time

    def decoding1(self, job: np.ndarray, machine: np.ndarray, machine_time: np.ndarray) -> Tuple:
        """
        普通调度的解码函数
        
        Args:
            job: 工序编码
            machine: 机器编码
            machine_time: 时间编码
            
        Returns:
            完工时间、负载均衡度、能耗和其他绘图所需数据
        """
        jobtime = np.zeros((1, self.order_num))  # 订单的完工时间
        tmm = np.zeros((1, self.machine_num))  # 某工序机器的完工时间
        tmmw = np.zeros((1, self.machine_num))  # 机器的工作时间

        startime = 0
        # list_M存放机器编号，list_S存放工序开始时间，list_W存放对应工序在对应机器上的加工时间
        list_M, list_S, list_W = [], [], []

        for i in range(job.shape[1]):
            svg, sig = int(job[0, i]), int(machine[0, i]) - 1

            if jobtime[0, svg] > 0:  # 如果不是订单的第一道工序                                
                startime = max(jobtime[0, svg], tmm[0, sig])
                tmm[0, sig] = startime + machine_time[0, i]
                jobtime[0, svg] = startime + machine_time[0, i]
            else:  # 如果是订单的第一道工序
                startime = tmm[0, sig]
                tmm[0, sig] = startime + machine_time[0, i]
                jobtime[0, svg] = startime + machine_time[0, i]

            tmmw[0, sig] += machine_time[0, i]
            list_M.append(machine[0, i])
            list_S.append(startime)
            list_W.append(machine_time[0, i])

        tmax = np.argmax(tmm[0]) + 1  # 结束最晚的机器

        # 计算完工时间
        C_finish = 0
        for i in range(len(jobtime[0])):
            if jobtime[0, i] <= self.delivery_time[int(job[0, i])]:
                C_finish += 0
            else:
                C_finish += abs(jobtime[0, i] - self.delivery_time[int(job[0, i])])

        T_finish = max(tmm[0])  # 最晚完工时间
        C_finish += T_finish

        # 计算能耗
        trest = tmm - tmmw  # 空闲时间
        # E_all = sum((tmmw * self.p1)[0]) + sum((trest * self.p2)[0])

        # 计算负载均衡度
        # 使用最大负载与最小负载之间的差值计算负载均衡度
        # 差值越小表示负载越均衡
        machine_workloads = tmmw[0]  # 每台机器的工作时间
        max_workload = np.max(machine_workloads)
        min_workload = np.min(machine_workloads)
        LB = max_workload - min_workload  # 负载均衡度 - 值越小表示越均衡

        # return C_finish, LB, E_all, list_M, list_S, list_W, tmax, T_finish
        return C_finish, LB, list_M, list_S, list_W, tmax, T_finish

    def decoding2(self, job: np.ndarray, machine: np.ndarray, machine_time: np.ndarray) -> Tuple:
        """
        重调度的解码过程
        
        Args:
            job: 工序编码
            machine: 机器编码
            machine_time: 时间编码
            
        Returns:
            解码结果：完工时间、机器负荷、偏移度等信息
        """
        jobtime = np.zeros((1, self.order_num))  # 订单的完工时间
        tmm = np.zeros((1, self.machine_num))  # 某工序机器的完工时间
        tmmw = np.zeros((1, self.machine_num))  # 机器的工作时间

        # 初始化机器完工时间，考虑当前正在加工的作业
        if self.jobing is not None:
            for job_info in self.jobing:
                m = int(job_info[1]) - 1
                tmm[0, m] = float(job_info[2])

        startime = 0
        list_M, list_S, list_W = [], [], []

        # 加载原始调度信息
        original_schedule = self.load_original_schedule()

        # 构建新的调度信息，用于计算偏移率
        new_schedule = {}

        for i in range(job.shape[1]):
            svg, sig = int(job[0, i]), int(machine[0, i]) - 1

            if jobtime[0, svg] > 0:  # 如果不是订单的第一道工序                                
                startime = max(jobtime[0, svg], tmm[0, sig])
                tmm[0, sig] = startime + machine_time[0, i]
                jobtime[0, svg] = startime + machine_time[0, i]
            else:  # 如果是订单的第一道工序
                startime = tmm[0, sig]
                tmm[0, sig] = startime + machine_time[0, i]
                jobtime[0, svg] = startime + machine_time[0, i]

            tmmw[0, sig] += machine_time[0, i]
            list_M.append(machine[0, i])
            list_S.append(startime)
            list_W.append(machine_time[0, i])

            # 记录工序的机器和时间信息，确保每个作业有一个任务列表
            job_id = int(job[0, i])
            if job_id not in new_schedule:
                new_schedule[job_id] = []

            task_info = (int(machine[0, i]), float(startime), float(startime + machine_time[0, i]))
            new_schedule[job_id].append(task_info)

        tmax = np.argmax(tmm[0]) + 1  # 结束最晚的机器

        # 计算完工时间
        C_finish = 0
        for i in range(len(jobtime[0])):
            if jobtime[0, i] <= self.delivery_time[i]:
                C_finish += 0
            else:
                C_finish += abs(jobtime[0, i] - self.delivery_time[i])

        T_finish = max(tmm[0])  # 最晚完工时间
        C_finish += T_finish

        # 计算能耗
        trest = tmm - tmmw

        # 计算偏移度
        shift_rate_info = self.analyze_shift_rate(original_schedule, new_schedule, machine_time)
        shift_rate = shift_rate_info.get("综合偏移度", 0)  # 保持变量名兼容性，实际已改为综合偏移度

        # 计算负载均衡度
        machine_workloads = tmmw[0]  # 每台机器的工作时间
        max_workload = np.max(machine_workloads)
        min_workload = np.min(machine_workloads)
        LB = max_workload - min_workload  # 负载均衡度

        return C_finish, LB, shift_rate, list_M, list_S, list_W, tmax, T_finish

    def load_original_schedule(self) -> Dict:
        """
        加载原始调度信息，用于计算偏移率
        
        Returns:
            原始调度信息字典 {订单ID: [(机器, 结束时间), ...]}
        """
        # 如果已经有缓存的原始调度信息，直接返回
        if self._original_schedule_cache is not None:
            return self._original_schedule_cache
        
        from config.config import FIRST_SCHEDULING_RESULT_PATH
        
        # 初始化空字典
        original_schedule = {}
        
        try:
            # 读取第一次调度结果文件
            data = pd.read_excel(FIRST_SCHEDULING_RESULT_PATH)
            
            # 处理每一行，提取订单编号、工序号、机器编号和完工时间
            for _, row in data.iterrows():
                try:
                    job_id = int(row['订单编号']) - 1
                    task_id = int(row['订单的工序号']) - 1
                    machine = int(row['机器编号'])
                    completion_time = row['工序完成时间']
                    
                    # 确保字典中有对应的键
                    if job_id not in original_schedule:
                        original_schedule[job_id] = []
                    
                    # 扩展列表，确保有足够的位置
                    while len(original_schedule[job_id]) <= task_id:
                        original_schedule[job_id].append(None)
                    
                    # 保存任务信息
                    original_schedule[job_id][task_id] = (machine, completion_time)
                except Exception as e:
                    print(f"处理行时出错: {row}, 错误: {e}")
            
            # 缓存结果到成员变量
            self._original_schedule_cache = original_schedule

            print(f"成功加载原始调度信息: {original_schedule}")
            
            return original_schedule
        except Exception as e:
            print(f"读取原始调度信息时出错: {e}")
            self._original_schedule_cache = {}
            return {}

    def draw1(self, filename: str, A: float, B: float, tmax: int, T_finish: float, colorfile: str) -> None:
        """
        绘制甘特图
        
        Args:
            filename: 结果文件名
            A, B, C: 目标函数值
            tmax: 最大任务数
            T_finish: 完工时间
            colorfile: 颜色文件名 (现已不使用，保留参数以兼容现有调用)
        """
        # 设置中文字体支持
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'SimSun', 'Arial Unicode MS', 'DejaVu Sans']
        plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

        # 过滤blended transforms警告
        import warnings
        warnings.filterwarnings("ignore", message="Blended transforms not yet supported")

        dd = pd.read_excel(filename)
        print(f"读取到的工序总数: {len(dd)}")  # 添加调试信息

        job = dd['订单编号'].values.reshape(1, -1)
        self.order_num = job.max()
        list_M = dd['机器编号'].tolist()
        list_S = dd['工序开始时间'].tolist()
        list_W = dd['工序加工时间'].tolist()

        # 计算每个工序的完工时间
        finish_times = [list_S[i] + list_W[i] for i in range(len(list_S))]

        # 重新计算实际的最大完工时间
        actual_finish_time = max(finish_times) if finish_times else T_finish
        print(f"传入的完工时间: {T_finish}, 计算得到的实际最大完工时间: {actual_finish_time}")

        # 取较大值作为甘特图的显示范围
        max_display_time = max(T_finish, actual_finish_time)

        job[0] = job[0] - 1

        # 计算实际使用的最大机器编号
        actual_max_machine = max(list_M) if list_M else self.machine_num
        print(f"最大机器编号: {actual_max_machine}")  # 添加调试信息

        figure, ax = plt.subplots(figsize=(25, 10))
        count = np.zeros((1, self.order_num))

        from config.config import colors

        # 记录实际绘制的工序数
        drawn_tasks = 0

        # 绘制每道工序
        for i in range(job.shape[1]):
            count[0, int(job[0, i])] += 1

            # 使用订单编号索引颜色列表，如果超出范围则循环使用颜色
            color_idx = int(job[0, i]) % len(colors)
            icolor = colors[color_idx]

            # 绘制工序条形图 - 使用barh而非bar以便准确获取位置
            bar_height = 0.5
            bar = ax.barh(y=list_M[i], width=list_W[i], height=bar_height,
                          left=list_S[i], color=icolor, edgecolor='black')

            # 获取绘制的条形图实际位置
            bar_y = bar[0].get_y()
            block_width = list_W[i]

            # 动态计算字体大小 (根据工序长度调整)
            label_text = f'{int(job[0, i] + 1)}.{int(count[0, int(job[0, i])])}'
            char_count = len(label_text)

            # 确定最大可用字体大小（基于时间块宽度和字符数）
            if block_width < 0.8:  # 非常小的块，不显示标签
                drawn_tasks += 1
                continue

            # 计算能放下所有字符所需的最小字体大小
            min_required_fontsize = 8  # 最小可读字体

            # 计算块宽度能支持的最大字体大小
            char_width_at_12pt = 0.6
            max_possible_fontsize = (block_width / (char_count * char_width_at_12pt)) * 12

            # 最终字体大小: 在可读范围内的最大可能值
            fontsize = min(20, max(min_required_fontsize, max_possible_fontsize))

            # 计算标签的位置 - 精确定位在时间块内部中央
            label_x = list_S[i] + block_width / 2
            label_y = bar_y + bar_height / 2

            # 只有当字体足够显示时才添加标签
            if fontsize >= min_required_fontsize:
                # 添加工序标签（居中显示，无背景）
                ax.text(label_x, label_y, label_text,
                        color='black', fontsize=fontsize, weight='bold',
                        ha='center', va='center')  # 水平和垂直居中

            drawn_tasks += 1

        print(f"实际绘制的工序数: {drawn_tasks}")  # 添加调试信息

        # 根据实际情况设置标记线和标签
        ax.axvline(x=actual_finish_time, color='black', linestyle='-.',
                   label=f'完工时间={actual_finish_time:.1f}')

        # if self.flag == 1 and self.insertpoint is not None:  # 重调度模式
        #     ax.axvline(x=self.insertpoint, color='black', linestyle='-.', label='重调度插入点')
        #     ax.axvline(x=actual_finish_time, color='black', linestyle='-.',
        #             label=f'完工时间={actual_finish_time:.1f}\n重调度部分时间成本={A:.1f}\n重调度部分机器负荷极差={B:.1f}\n重调度偏移率={C*100:.2f}%')
        # else:  # 普通调度模式
        #     ax.axvline(x=actual_finish_time, color='black', linestyle='-.',
        #             label=f'完工时间={actual_finish_time:.1f}\n时间成本={A:.1f}\n机器负荷={B:.1f}\n能耗={C:.1f}')

        # 设置图表格式
        font1 = {'family': 'SimHei', 'weight': 'bold', 'size': 22}
        ax.set_xlabel("加工时间", font1)
        ax.set_title(f'Mk01, 完工时间={A:.0f}, 机器负载极差={B:.0f}', font1)
        ax.set_ylabel("机器", font1)

        # 直接创建与实际机器编号相匹配的Y轴刻度
        machine_labels = []
        machine_ticks = []
        for i in range(1, int(actual_max_machine) + 1):
            machine_ticks.append(i)
            machine_labels.append(f'M{i}')

        plt.yticks(machine_ticks, machine_labels)

        # 设置x轴范围确保所有工序可见，包括重调度后的工序
        plt.xlim(0, max_display_time * 1.1)

        # 设置y轴范围确保所有机器行可见
        plt.ylim(0.5, actual_max_machine + 0.5)

        plt.tick_params(labelsize=22)
        labels = ax.get_xticklabels()
        [label.set_fontname('SimHei') for label in labels]

        # 将图例固定在右上角
        plt.legend(prop={'family': 'SimHei', 'size': 16}, loc='upper right')

        plt.tight_layout()

        # 保存到实验目录
        exp_dir = get_experiment_dir()
        base_filename = os.path.basename(filename).split('.')[0]
        save_path = os.path.join(exp_dir, f"{base_filename}_gantt.svg")
        plt.savefig(save_path, bbox_inches='tight')
        print(f"甘特图已保存至: {save_path}")

        # 显示图表
        # plt.show()

    def draw2(self, filename: str, A: float, B: float, C: float, tmax: int, T_finish: float, colorfile: str) -> None:
        """
        绘制甘特图

        Args:
            filename: 结果文件名
            A, B, C: 目标函数值
            tmax: 最大任务数
            T_finish: 完工时间
            colorfile: 颜色文件名 (现已不使用，保留参数以兼容现有调用)
        """
        # 设置中文字体支持
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'SimSun', 'Arial Unicode MS', 'DejaVu Sans']
        plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

        # 过滤blended transforms警告
        import warnings
        warnings.filterwarnings("ignore", message="Blended transforms not yet supported")

        dd = pd.read_excel(filename)
        print(f"读取到的工序总数: {len(dd)}")  # 添加调试信息

        job = dd['订单编号'].values.reshape(1, -1)
        self.order_num = job.max()
        list_M = dd['机器编号'].tolist()
        list_S = dd['工序开始时间'].tolist()
        list_W = dd['工序加工时间'].tolist()

        # 计算每个工序的完工时间
        finish_times = [list_S[i] + list_W[i] for i in range(len(list_S))]

        # 重新计算实际的最大完工时间
        actual_finish_time = max(finish_times) if finish_times else T_finish
        print(f"传入的完工时间: {T_finish}, 计算得到的实际最大完工时间: {actual_finish_time}")

        # 取较大值作为甘特图的显示范围
        max_display_time = max(T_finish, actual_finish_time)

        job[0] = job[0] - 1

        # 计算实际使用的最大机器编号
        actual_max_machine = max(list_M) if list_M else self.machine_num
        print(f"最大机器编号: {actual_max_machine}")  # 添加调试信息

        figure, ax = plt.subplots(figsize=(25, 10))
        count = np.zeros((1, self.order_num))

        from config.config import colors

        # 记录实际绘制的工序数
        drawn_tasks = 0

        # 绘制每道工序
        for i in range(job.shape[1]):
            # if int(job[0, i] + 1) == 999:
            #     # 绘制工序条形图 - 使用barh而非bar以便准确获取位置
            #     bar_height = 0.5
            #     bar = ax.barh(y=list_M[i], width=list_W[i], height=bar_height,
            #                   left=list_S[i], color='grey', edgecolor='black')
            #     continue

            count[0, int(job[0, i])] += 1

            # 使用订单编号索引颜色列表，如果超出范围则循环使用颜色
            color_idx = int(job[0, i]) % len(colors)
            icolor = colors[color_idx]

            # 绘制工序条形图 - 使用barh而非bar以便准确获取位置
            bar_height = 0.5
            bar = ax.barh(y=list_M[i], width=list_W[i], height=bar_height,
                          left=list_S[i], color=icolor, edgecolor='black')

            # 获取绘制的条形图实际位置
            bar_y = bar[0].get_y()
            block_width = list_W[i]

            # 动态计算字体大小 (根据工序长度调整)
            label_text = f'{int(job[0, i] + 1)}.{int(count[0, int(job[0, i])])}'
            char_count = len(label_text)

            # 确定最大可用字体大小（基于时间块宽度和字符数）
            if block_width < 0.8:  # 非常小的块，不显示标签
                drawn_tasks += 1
                continue

            # 计算能放下所有字符所需的最小字体大小
            min_required_fontsize = 8  # 最小可读字体

            # 计算块宽度能支持的最大字体大小
            char_width_at_12pt = 0.6
            max_possible_fontsize = (block_width / (char_count * char_width_at_12pt)) * 12

            # 最终字体大小: 在可读范围内的最大可能值
            fontsize = min(20, max(min_required_fontsize, max_possible_fontsize))

            # 计算标签的位置 - 精确定位在时间块内部中央
            label_x = list_S[i] + block_width / 2
            label_y = bar_y + bar_height / 2

            # 只有当字体足够显示时才添加标签
            if fontsize >= min_required_fontsize:
                # 添加工序标签（居中显示，无背景）
                ax.text(label_x, label_y, label_text,
                        color='black', fontsize=fontsize, weight='bold',
                        ha='center', va='center')  # 水平和垂直居中

            drawn_tasks += 1

        print(f"实际绘制的工序数: {drawn_tasks}")  # 添加调试信息

        # # 根据实际情况设置标记线和标签
        ax.axvline(x=self.insertpoint, color='red', linestyle='-.', label='作业插入点')
        ax.axvline(x=actual_finish_time, color='black', linestyle='-.',
                   label=f'总完工时间={actual_finish_time:.0f}\n')

        # if self.flag == 1 and self.insertpoint is not None:  # 重调度模式
        #     ax.axvline(x=self.insertpoint, color='black', linestyle='-.', label='重调度插入点')
        #     ax.axvline(x=actual_finish_time, color='black', linestyle='-.',
        #                label=f'完工时间={actual_finish_time:.1f}\n重调度部分时间成本={A:.1f}\n重调度部分机器负荷极差={B:.1f}\n重调度偏移率={C * 100:.2f}%')
        # else:  # 普通调度模式
        #     ax.axvline(x=actual_finish_time, color='black', linestyle='-.',
        #                label=f'完工时间={actual_finish_time:.1f}\n时间成本={A:.1f}\n机器负荷={B:.1f}\n能耗={C:.1f}')

        # 设置图表格式
        font1 = {'family': 'SimHei', 'weight': 'bold', 'size': 22}
        ax.set_xlabel("加工时间", font1)
        ax.set_title(f'Mk01, 重调度部分完工时间={A:.0f}, 重调度部分机器负载极差={B:.0f}, 重调度偏移度={C:.0f}',
                     font1)
        ax.set_ylabel("机器", font1)

        # 直接创建与实际机器编号相匹配的Y轴刻度
        machine_labels = []
        machine_ticks = []
        for i in range(1, int(actual_max_machine) + 1):
            machine_ticks.append(i)
            machine_labels.append(f'M{i}')

        plt.yticks(machine_ticks, machine_labels)

        # 设置x轴范围确保所有工序可见，包括重调度后的工序
        plt.xlim(0, max_display_time * 1.1)

        # 设置y轴范围确保所有机器行可见
        plt.ylim(0.5, actual_max_machine + 0.5)

        plt.tick_params(labelsize=22)
        labels = ax.get_xticklabels()
        [label.set_fontname('SimHei') for label in labels]

        # 将图例固定在右上角
        plt.legend(prop={'family': 'SimHei', 'size': 16}, loc='upper right')

        plt.tight_layout()

        # 保存到实验目录
        exp_dir = get_experiment_dir()
        base_filename = os.path.basename(filename).split('.')[0]
        save_path = os.path.join(exp_dir, f"{base_filename}_gantt.svg")
        plt.savefig(save_path, bbox_inches='tight')
        print(f"甘特图已保存至: {save_path}")

    def draw3(self, filename: str, A: float, B: float, C: float, tmax: int, T_finish: float, colorfile: str) -> None:
        """
        绘制甘特图

        Args:
            filename: 结果文件名
            A, B, C: 目标函数值
            tmax: 最大任务数
            T_finish: 完工时间
            colorfile: 颜色文件名 (现已不使用，保留参数以兼容现有调用)
        """
        # 设置中文字体支持
        plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'SimSun', 'Arial Unicode MS', 'DejaVu Sans']
        plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

        # 过滤blended transforms警告
        import warnings
        warnings.filterwarnings("ignore", message="Blended transforms not yet supported")

        dd = pd.read_excel(filename)
        print(f"读取到的工序总数: {len(dd)}")  # 添加调试信息

        job = dd['订单编号'].values.reshape(1, -1)
        self.order_num = job.max()
        list_M = dd['机器编号'].tolist()
        list_S = dd['工序开始时间'].tolist()
        list_W = dd['工序加工时间'].tolist()

        # 计算每个工序的完工时间
        finish_times = [list_S[i] + list_W[i] for i in range(len(list_S))]

        # 重新计算实际的最大完工时间
        actual_finish_time = max(finish_times) if finish_times else T_finish
        print(f"传入的完工时间: {T_finish}, 计算得到的实际最大完工时间: {actual_finish_time}")

        # 取较大值作为甘特图的显示范围
        max_display_time = max(T_finish, actual_finish_time)

        job[0] = job[0] - 1

        # 计算实际使用的最大机器编号
        actual_max_machine = max(list_M) if list_M else self.machine_num
        print(f"最大机器编号: {actual_max_machine}")  # 添加调试信息

        figure, ax = plt.subplots(figsize=(25, 10))
        count = np.zeros((1, self.order_num))

        from config.config import colors

        # 记录实际绘制的工序数
        drawn_tasks = 0

        # 绘制每道工序
        for i in range(job.shape[1]):

            if int(job[0, i] + 1) == 999:
                # 绘制工序条形图 - 使用barh而非bar以便准确获取位置
                bar_height = 0.5
                bar = ax.barh(y=list_M[i], width=list_W[i], height=bar_height,
                              left=list_S[i], color='black', edgecolor='black')
                continue

            count[0, int(job[0, i])] += 1

            # 使用订单编号索引颜色列表，如果超出范围则循环使用颜色
            color_idx = int(job[0, i]) % len(colors)
            icolor = colors[color_idx]

            # 绘制工序条形图 - 使用barh而非bar以便准确获取位置
            bar_height = 0.5
            bar = ax.barh(y=list_M[i], width=list_W[i], height=bar_height,
                          left=list_S[i], color=icolor, edgecolor='black')

            # 获取绘制的条形图实际位置
            bar_y = bar[0].get_y()
            block_width = list_W[i]

            # 动态计算字体大小 (根据工序长度调整)
            label_text = f'{int(job[0, i] + 1)}.{int(count[0, int(job[0, i])])}'
            char_count = len(label_text)

            # 确定最大可用字体大小（基于时间块宽度和字符数）
            if block_width < 0.8:  # 非常小的块，不显示标签
                drawn_tasks += 1
                continue

            # 计算能放下所有字符所需的最小字体大小
            min_required_fontsize = 8  # 最小可读字体

            # 计算块宽度能支持的最大字体大小
            char_width_at_12pt = 0.6
            max_possible_fontsize = (block_width / (char_count * char_width_at_12pt)) * 12

            # 最终字体大小: 在可读范围内的最大可能值
            fontsize = min(20, max(min_required_fontsize, max_possible_fontsize))

            # 计算标签的位置 - 精确定位在时间块内部中央
            label_x = list_S[i] + block_width / 2
            label_y = bar_y + bar_height / 2

            # 只有当字体足够显示时才添加标签
            if fontsize >= min_required_fontsize:
                # 添加工序标签（居中显示，无背景）
                ax.text(label_x, label_y, label_text,
                        color='black', fontsize=fontsize, weight='bold',
                        ha='center', va='center')  # 水平和垂直居中

            drawn_tasks += 1

        print(f"实际绘制的工序数: {drawn_tasks}")  # 添加调试信息

        # # 根据实际情况设置标记线和标签
        ax.axvline(x=self.insertpoint, color='red', linestyle='-.', label='预测性维护开始时间')
        ax.axvline(x=actual_finish_time, color='black', linestyle='-.',
                   label=f'总完工时间={actual_finish_time:.1f}\n')

        # if self.flag == 1 and self.insertpoint is not None:  # 重调度模式
        #     ax.axvline(x=self.insertpoint, color='black', linestyle='-.', label='重调度插入点')
        #     ax.axvline(x=actual_finish_time, color='black', linestyle='-.',
        #                label=f'完工时间={actual_finish_time:.1f}\n重调度部分时间成本={A:.1f}\n重调度部分机器负荷极差={B:.1f}\n重调度偏移率={C * 100:.2f}%')
        # else:  # 普通调度模式
        #     ax.axvline(x=actual_finish_time, color='black', linestyle='-.',
        #                label=f'完工时间={actual_finish_time:.1f}\n时间成本={A:.1f}\n机器负荷={B:.1f}\n能耗={C:.1f}')

        # 设置图表格式
        font1 = {'family': 'SimHei', 'weight': 'bold', 'size': 22}
        ax.set_xlabel("加工时间", font1)
        ax.set_title(f'Mk01, 重调度部分完工时间={A:.0f}, 重调度部分机器负载极差={B:.0f}, 重调度偏移度={C:.0f}',
                     font1)
        ax.set_ylabel("机器", font1)

        # 直接创建与实际机器编号相匹配的Y轴刻度
        machine_labels = []
        machine_ticks = []
        for i in range(1, int(actual_max_machine) + 1):
            machine_ticks.append(i)
            machine_labels.append(f'M{i}')

        plt.yticks(machine_ticks, machine_labels)

        # 设置x轴范围确保所有工序可见，包括重调度后的工序
        plt.xlim(0, max_display_time * 1.1)

        # 设置y轴范围确保所有机器行可见
        plt.ylim(0.5, actual_max_machine + 0.5)

        plt.tick_params(labelsize=22)
        labels = ax.get_xticklabels()
        [label.set_fontname('SimHei') for label in labels]

        # 将图例固定在右上角
        plt.legend(prop={'family': 'SimHei', 'size': 16}, loc='upper right')

        plt.tight_layout()

        # 保存到实验目录
        exp_dir = get_experiment_dir()
        base_filename = os.path.basename(filename).split('.')[0]
        save_path = os.path.join(exp_dir, f"{base_filename}_gantt.svg")
        plt.savefig(save_path, bbox_inches='tight')
        print(f"甘特图已保存至: {save_path}")

    def get_result(self, job: np.ndarray, machine: np.ndarray, machine_time: np.ndarray,
                   colorfile: str, resultsfile: str) -> None:
        """
        生成调度结果并绘制甘特图
        
        Args:
            job: 工序编码
            machine: 机器编码
            machine_time: 时间编码
            colorfile: 颜色文件
            resultsfile: 结果文件名
        """
        from src.utils import get_experiment_dir
        import os

        # 调试
        # a = job

        # 获取实验目录
        exp_dir = get_experiment_dir()
        result_path = os.path.join(exp_dir, resultsfile)
        # ----------------------------------------------------------------------------------------------------------------------
        if self.flag == 0:
            # 普通调度
            # C_finish, LB, E_all, list_M, list_S, list_W, tmax, T_finish = self.decoding1(job, machine, machine_time)
            C_finish, LB, list_M, list_S, list_W, tmax, T_finish = self.decoding1(job, machine, machine_time)

            # 生成结果表格
            procedure = []

            count = np.zeros((1, self.order_num))
            for i in range(job.shape[1]):
                count[0, int(job[0, i])] += 1
                procedure.append(count[0, int(job[0, i])])

            result = pd.DataFrame({
                '订单编号': job[0] + 1,
                '订单的工序号': procedure,
                '机器编号': list_M,
                '工序开始时间': list_S,
                '工序加工时间': list_W
            })
            result['工序完成时间'] = result['工序开始时间'] + result['工序加工时间']

            # 保存到实验目录
            result = result.sort_values(by=['工序开始时间', '工序完成时间']).to_excel(result_path, index=False)

            # 同时保存一份到项目根目录，用于重调度
            project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
            fixed_path = os.path.join(project_root, '普通调度结果.xlsx')
            result = pd.DataFrame({
                '订单编号': job[0] + 1,
                '订单的工序号': procedure,
                '机器编号': list_M,
                '工序开始时间': list_S,
                '工序加工时间': list_W
            })
            result['工序完成时间'] = result['工序开始时间'] + result['工序加工时间']
            result.sort_values(by=['工序开始时间', '工序完成时间']).to_excel(fixed_path, index=False)
            print(f"初始调度结果已额外保存至固定路径: {fixed_path}")

            # 绘制甘特图
            # self.draw(result_path, C_finish, LB, E_all, tmax, T_finish, colorfile)
            self.draw1(result_path, C_finish, LB, tmax, T_finish, colorfile)
        # ----------------------------------------------------------------------------------------------------------------------
        elif self.flag == 1:
            # 插单重调度
            C_finish, LB, shift_rate, list_M, list_S, list_W, tmax, T_finish = self.decoding2(job, machine,
                                                                                              machine_time)
            # 生成结果表格
            procedure = []

            c = job
            d = job.shape[1]

            count = np.zeros((1, self.order_num))
            for i in range(job.shape[1]):
                count[0, int(job[0, i])] += 1
                procedure.append(count[0, int(job[0, i])])

            # 生成结果表格
            result = pd.DataFrame({
                '订单编号': job[0] + 1,
                '订单的工序号': procedure,
                '机器编号': list_M,  # 机器选择子问题
                '工序开始时间': list_S,  # 开始时间
                '工序加工时间': list_W
            })

            b = result

            # 保存原始结果到Excel文件
            try:
                # 获取项目根目录
                project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

                # 创建实验目录（如果不存在）
                exp_dir = get_experiment_dir()

                # 保存原始结果到实验目录
                original_result_path = os.path.join(exp_dir, 'original_result.xlsx')
                b['工序完成时间'] = b['工序开始时间'] + b['工序加工时间']
                b.to_excel(original_result_path, index=False)

                # 同时保存一份到项目根目录
                fixed_original_path = os.path.join(project_root, 'original_result.xlsx')
                b.to_excel(fixed_original_path, index=False)

                print(f"原始结果数据已保存至: {original_result_path}")
                print(f"原始结果数据已额外保存至: {fixed_original_path}")
            except Exception as e:
                print(f"保存原始结果数据时出错: {e}")

            # 处理订单编号和工序号
            for u in result['订单编号'].unique():
                tmp = self.jobdic[int(u)][1] - 1  # 这里需要减1哦,因为jobdic字典,记录的以及是下一个编号了,如果再加tmp就多加了一次
                # jobdic保存的是订单/作业进行重调度的最小编号
                result.loc[result['订单编号'] == u, '订单的工序号'] = result.loc[
                                                                          result['订单编号'] == u, '订单的工序号'] + tmp
            result['订单编号'] = result['订单编号'].apply(lambda x: self.jobdic[int(x)][0])

            # 调整时间和合并结果
            result['工序开始时间'] = result['工序开始时间'].apply(lambda x: x + self.insertpoint)
            result['工序完成时间'] = result['工序开始时间'] + result['工序加工时间']
            result = pd.concat([self.nojob, result])
            result = result.sort_values(by=['工序开始时间', '工序完成时间']).to_excel(result_path, index=False)

            #
            # # 创建维护作业并确保列名一致
            # maintenance = pd.DataFrame([[-1, MAINTENANCE_MACHINE, MAINTENANCE_DURATION]],
            #                            columns=['订单编号', '机器编号', '剩余加工时间'])
            #
            # # 连接两个DataFrame并重置索引
            # res = pd.concat([result, maintenance], ignore_index=True)
            #
            # result = res.sort_values(by=['工序开始时间', '工序完成时间']).to_excel(result_path, index=False)

            # 绘制甘特图
            self.draw2(result_path, C_finish, LB, shift_rate, tmax, T_finish, colorfile)
        # ----------------------------------------------------------------------------------------------------------------------
        elif self.flag == 2:
            # 重调度
            C_finish, LB, shift_rate, list_M, list_S, list_W, tmax, T_finish = self.decoding2(job, machine,
                                                                                              machine_time)

            # 生成结果表格
            procedure = []
            count = np.zeros((1, self.order_num))
            for i in range(job.shape[1]):
                count[0, int(job[0, i])] += 1
                procedure.append(count[0, int(job[0, i])])

            # 生成结果表格
            result = pd.DataFrame({
                '订单编号': job[0] + 1,
                '订单的工序号': procedure,  #
                '机器编号': list_M,
                '工序开始时间': list_S,
                '工序加工时间': list_W
            })

            # 处理订单编号和工序号
            for u in result['订单编号'].unique():
                tmp = self.jobdic[int(u)][1] - 1  # 这里需要减1哦,因为jobdic字典,记录的以及是下一个编号了,如果再加tmp就多加了一次
                result.loc[result['订单编号'] == u, '订单的工序号'] = result.loc[
                                                                          result['订单编号'] == u, '订单的工序号'] + tmp
            result['订单编号'] = result['订单编号'].apply(lambda x: self.jobdic[int(x)][0])

            # 调整时间和合并结果
            result['工序开始时间'] = result['工序开始时间'].apply(lambda x: x + self.insertpoint)
            result['工序完成时间'] = result['工序开始时间'] + result['工序加工时间']
            result = pd.concat([self.nojob, result])
            # result = result.sort_values(by=['工序开始时间', '工序完成时间']).to_excel(result_path, index=False)

            # 创建维护作业并确保列名一致

            maintenance = pd.DataFrame([[999, 999, MAINTENANCE_MACHINE, MAINTENANCE_TIME, MAINTENANCE_DURATION,
                                         MAINTENANCE_TIME + MAINTENANCE_DURATION]],
                                       columns=['订单编号', '订单的工序号', '机器编号', '工序开始时间', '工序加工时间',
                                                '工序完成时间'])

            # 连接两个DataFrame并重置索引
            res = pd.concat([result, maintenance], ignore_index=True)

            result = res.sort_values(by=['工序开始时间', '工序完成时间']).to_excel(result_path, index=False)

            # 绘制甘特图
            self.draw3(result_path, C_finish, LB, shift_rate, tmax, T_finish, colorfile)

        print(f"调度结果已保存至: {result_path}")

    def save_scheduling_path(self, result_path: str) -> None:
        """
        保存调度结果表格的绝对路径到配置文件
        
        Args:
            result_path: 调度结果文件的绝对路径
        """
        import os

        # 创建配置文件目录（如果不存在）
        config_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'config')
        if not os.path.exists(config_dir):
            os.makedirs(config_dir)

        # 保存路径到文件
        path_file = os.path.join(config_dir, 'scheduling_path.txt')
        with open(path_file, 'w') as f:
            f.write(result_path)

        print(f"已保存调度路径到: {path_file}")

    def analyze_shift_rate(self, original_schedule: Dict, new_schedule: Dict, machine_time=None) -> Dict:
        """
        计算综合偏移度 (SI) = α·工序偏离度 (PD) + β·机器偏移度 (MD)
        
        Args:
            original_schedule: 原始调度信息
            new_schedule: 新调度信息
            machine_time: 机器加工时间矩阵，如果未提供则仅计算机器变化率
            
        Returns:
            偏移度分析结果
        """
        # 工序偏离度 (PD) 计算
        # PD = ∑∑|S'ij - Sij|，即重调度前后工序开始时间的偏差总和
        process_deviation = 0
        
        # 机器偏移度 (MD) 计算
        # 统计原始调度和新调度中每台机器上的工序数量
        original_machine_count = {}  # 原计划中每台机器上的工序数
        new_machine_count = {}       # 新计划中每台机器上的工序数
        
        # 惩罚系数设置
        alpha = 0.5  # 工序开始时间变化的惩罚系数
        beta = 0.5   # 机器惩罚系数
        
        # 如果原始调度信息为空，无法计算偏移度
        if not original_schedule:
            print("警告: 原始调度信息为空，无法计算准确的偏移度")
            return {
                "工序偏离度": 0,
                "机器偏移度": 0,
                "综合偏移度": 0
            }
            
        # 计算工序偏离度
        for job_id in original_schedule:
            if job_id in new_schedule:
                orig_tasks = original_schedule[job_id]
                new_tasks = new_schedule[job_id]
                
                comparable_tasks = min(len(orig_tasks), len(new_tasks))
                
                for j in range(comparable_tasks):
                    if orig_tasks[j] is not None and j < len(new_tasks):
                        orig_machine, orig_completion_time = orig_tasks[j]
                        new_machine, new_start, new_end = new_tasks[j]
                        
                        # 计算工序开始时间的偏差
                        # 从完工时间和处理时间计算原始开始时间
                        process_time = new_end - new_start
                        orig_start = orig_completion_time - process_time
                        
                        # 计算开始时间偏离
                        process_deviation += abs(new_start - orig_start)
                        
                        # 更新机器工序计数
                        if orig_machine not in original_machine_count:
                            original_machine_count[orig_machine] = 0
                        original_machine_count[orig_machine] += 1
                        
                        if new_machine not in new_machine_count:
                            new_machine_count[new_machine] = 0
                        new_machine_count[new_machine] += 1
        
        # 计算机器偏移度：∑|N'k - Nk|
        machine_deviation = 0
        all_machines = set(list(original_machine_count.keys()) + list(new_machine_count.keys()))
        
        for machine in all_machines:
            orig_count = original_machine_count.get(machine, 0)
            new_count = new_machine_count.get(machine, 0)
            machine_deviation += abs(new_count - orig_count)
        
        # 计算综合偏移度：SI = α·PD + β·MD
        comprehensive_deviation = alpha * process_deviation + beta * machine_deviation

        normalized_deviation = comprehensive_deviation
        
        # # 归一化处理，使综合偏移度的值在0到1之间
        # # 计算最大可能的偏移度（作为归一化分母）
        # max_tasks = sum(max(len(tasks) for job_id, tasks in original_schedule.items() if tasks))
        # max_process_deviation = max_tasks * 100  # 假设时间单位最大偏差为100
        # max_machine_deviation = max_tasks * 2    # 最差情况下，每个任务都改变了机器
        
        # # 归一化
        # normalization_factor = alpha * max_process_deviation + beta * max_machine_deviation
        # if normalization_factor > 0:
        #     normalized_deviation = comprehensive_deviation / normalization_factor
        # else:
        #     normalized_deviation = 0
        
        result = {
            "工序偏离度": process_deviation,
            "机器偏移度": machine_deviation,
            "综合偏移度": normalized_deviation,
            "总体偏移率": normalized_deviation  # 保持兼容现有接口
        }
        
        return result
